"""Various utility functions."""

__author__ = 'Ryan LIANG'
__version__ = '0.1.0'

import inspect
import os
import pywbem
import re
import sys
import types
import unittest

TEST_MODULE_DIR = './TestCases/py/'

def wbemConnect():
    """Sets up the WBEM connection to server and return the connection object."""
    server = os.getenv('cimserver')
    username = os.getenv('cimusr')
    password = os.getenv('cimpwd')
    namespace = os.getenv('cimns')
    connection = pywbem.WBEMConnection(server, (username, password), namespace)
    return connection


def _merge2Dict(dict1, dict2):
    # dict1 and dict2 are all formatting like: {module: {suite: [case1, case2,]}},
    # or are all formatting like: {suite: [case1, case2,]}
    for key in dict2:
        if key not in dict1:
            dict1[key] = dict2[key]
        else:
            if(type(dict2[key]) is dict):
                _merge2Dict(dict1[key], dict2[key])
            elif(type(dict2[key]) is list):
                dict1[key] += dict2[key]
            else:
                dict1[key] = dict2[key]


def _parseTestCases(casesStr):
    """Parses the string into test cases.
    The input string formats like:
        module=Demo1:suite=ComponentTest_Demo1,cases=test_case11,test_case12;
                     suite=UnitTest_Demo1,cases=test_case21,test_case22|
        module=Demo2:suite=UnitTest_Demo2,cases=test_case31,test_case32.
    The output is a dict {module: {suite: [cases]}}
    """
    result = dict()
    modulesList = casesStr.split('|')
    for i in modulesList:
        temp = i.split(':')
        if temp[0] == 'module=*':
            return {'*': {'*': ['*']}}
        if len(temp) != 2:
            continue
        if re.match('module=.*', temp[0]):
            moduleName = re.match('module=(.*)', temp[0]).group(1)
            if moduleName.strip() == '':
                continue
        else:
            continue

        suiteDict = {}
        suitesList = temp[1].split(';')
        for j in suitesList:
            if re.match('suite=.*,cases=.*', j):
                match = re.match('suite=(.*),cases\=(.*)', j)
                suiteName = match.group(1)
                casesList = [case for case in match.group(2).split(',') if case.strip() != '']
                if suiteName.strip() == '' or len(casesList) == 0:
                    continue
                _merge2Dict(suiteDict, {suiteName: casesList})
            elif re.match('suite=*', j):
                _merge2Dict(suiteDict, {'*': ['*']})
            else:
                continue
        
        if len(suiteDict) == 0:
            continue
        
        _merge2Dict(result, {moduleName: suiteDict})
    
    if '*' in result.keys():
        return {'*': {'*': ['*']}}
    for moduleKey in result:
        if '*' in result[moduleKey].keys():
            result[moduleKey] = {'*': ['*']}
        else:
            for suiteKey in result[moduleKey]:
                if '*' in result[moduleKey][suiteKey]:
                    result[moduleKey][suiteKey] = ['*']
    
    return result


def loadTestCases(casesStr, caseFilePath = None):
    if caseFilePath:
        with open(caseFilePath, 'r') as fileStream:
            for line in fileStream:
                lineList = line.split(':')
                if len(lineList) != 3:
                    continue
                lineStr = 'module=%s:suite=%s,cases=%s' \
                          % (lineList[0].strip(), lineList[1].strip(), lineList[2].strip())
                casesStr += lineStr + '|'
    allCasesDict = _parseTestCases(casesStr)
    resultSuite = unittest.TestSuite()
    for module, suiteDict in allCasesDict.iteritems():
        if module == '*':
            entries = os.listdir(TEST_MODULE_DIR)
            for entry in entries:
                if os.path.isdir(os.path.join(TEST_MODULE_DIR, entry)):
                    sys.path.insert(0, TEST_MODULE_DIR + entry)
                    filesList = os.listdir(TEST_MODULE_DIR + entry)
                    for pyFile in filesList:
                        if os.path.isfile(os.path.join(TEST_MODULE_DIR, entry, pyFile)) \
                        and pyFile.endswith('.py'):
                            module = __import__(pyFile[:-3])
                            resultSuite.addTest(unittest.TestLoader().loadTestsFromModule(module))
        else:
            for suite, casesList in suiteDict.iteritems():
                sys.path.insert(0, TEST_MODULE_DIR + module)
                if suite == '*':
                    filesList = os.listdir(TEST_MODULE_DIR + module)
                    for pyFile in filesList:
                        if os.path.isfile(os.pat.join(TEST_MODULE_DIR, module, pyFile)) \
                        and pyFile.endswith('.py'):
                            module = __import__(pyFile[:-3])
                            resultSuite.addTest(unittest.TestLoader().loadTestsFromModule(module))
                else:
                    module = __import__(suite)
                    suiteClass = getattr(module, suite)
                    if '*' in casesList:
                        resultSuite.addTest(unittest.TestLoader().loadTestsFromTestCase(suiteClass))
                    else:
                        for eachCase in casesList:
                            resultSuite.addTest(suiteClass(eachCase))
    return resultSuite

    
def _parseCasesFromFile(filePath):
    casesStr = ''
    with open(filePath, 'r') as fileStream:
        for line in fileStream:
            lineList = line.split(':')
            if len(lineList) != 3:
                continue
            lineStr = 'module=%s:suite=%s,cases=%s' \
                      % (lineList[0].strip(), lineList[1].strip(), lineList[2].strip())
            casesStr += lineStr + '|'
    return loadTestCases(casesStr)


def _getObjectName(object):
    name = None
    if type(object) is types.TypeType:
        name = object.__name__
    elif isinstance(object, unittest.TestCase):
        name = object.__class__.__name__
    return name


def logEnter(object):
    name = _getObjectName(object)
    print('%s.%s() -- Entering' % (name, inspect.stack()[1][3]))
    

def logExit(object):
    name = _getObjectName(object)
    print('%s.%s() -- Exiting' % (name, inspect.stack()[1][3]))
    

def logError(message):
    print('[ERROR] %s' % message)
    

def queryAssociatedInstances(wbemConnect, leftInst, associateClass, rightClass):
    return wbemConnect.Associators(leftInst.path,
                                   AssocClass = associateClass,
                                   ResultClass = rightClass)
    

def coverAssociationFromLeftToRight(wbemConnect, leftClassName, assoClassName, rightClassName):
    try:
        leftList = wbemConnect.EnumerateInstances(leftClassName)
    except Exception as ex:
        logError('Error occurs when enumerating class <%s>. Error Message: %s'
                      % (leftClassName, str(ex)))
        return False
    print('The instances of class <%s> can be enumerated.' % leftClassName)
    fail = False
    for leftInst in leftList:
        try:
            rightList = queryAssociatedInstances(wbemConnect, leftInst,
                                                 assoClassName, rightClassName)
            for rightInst in rightList:
                reLeftList = queryAssociatedInstances(wbemConnect, rightInst,
                                                      assoClassName, leftClassName)
                if leftInst not in reLeftList:
                    logError("""Error occurs when enumerating associations from <%s> """
                                  """to <%s> via <%s>."""
                                % (rightInst.classname, leftInst.classname, assoClassName))
                    fail = True
        except Exception as ex:
            logError("""Error occurs when enumerating associations from <%s> to <%s> """
                          """via <%s>. Error Message: %s"""
                          % (leftInst.classname, rightClassName, assoClassName, str(ex)))
            fail = True
            continue
    if not fail:
        print('The associations from <%s> to <%s> via <%s> can be enumerated.'
              % (leftClassName, rightClassName, assoClassName))
    return not fail


def coverAllAssociationship(wbemConnect, associationshipList):
    fail = False
    for (leftClassName, assoClassName, rightClassName) in associationshipList:
        try:
            wbemConnect.EnumerateInstances(assoClassName)
        except Exception as ex:
            logError('Error occurs when enumerating class <%s>. Error Message: %s'
                          % (assoClassName, str(ex)))
            fail = True
            continue
        print('The instances of class <%s> can be enumerated.' % (assoClassName))
        
        f1 = coverAssociationFromLeftToRight(wbemConnect, leftClassName, assoClassName, rightClassName)            
        f2 = coverAssociationFromLeftToRight(wbemConnect, rightClassName, assoClassName, leftClassName)
        
        fail = not (f1 and f2)
    return not fail


def getInstancesWithProperty(instancesList, propertyName, propertyValue):
    resultList = []
    for item in instancesList:
        if item.properties[propertyName].value == propertyValue:
            resultList.append(item)
    return resultList


def detailInfoOfInstance(instance):
    """Returns the detail information of instance."""
    
    if (not isinstance(instance, pywbem.CIMInstance)) and \
        (not isinstance(instance, pywbem.CIMInstanceName)):
        return instance
    else:
        detailInfo = "Instance detail:"
        for key in instance.keys():
            detailInfo += "\n    {}: {}".format(key, instance.__getitem__(key))
        return detailInfo
    

## Returns the detail information of instance.
## The parameter 'instances' can be a list of instances. If so, detail information
## of all instances will be returned. 
def detailInfo(instances):
    """Returns the detail information of instance.
    The parameter 'instances' can be a list of instances. If so, detail information
    of all instances will be returned. """
    
    if type(instances) is list:
        fullDetail = ""
        for index, each in enumerate(instances, 1):
            fullDetail += "\n  Index <{}>: {}" \
                          .format(index, detailInfoOfInstance(each))
        return fullDetail
    else:
        return detailInfoOfInstance(instances)


if __name__ == '__main__':
    #string = """module=m1:suite=s1,cases=c1,c2,c3;suite=s12,cases=c12,c13|
    #module=m2:suite=s2,cases=c4,c5,c6|module=m888:suite=s3,cases=c7|module=m2:suite=s2,cases=|
    #module=m1:suite=,cases=c9|module=:suite=s3,cases=c10|module=m1:suite=s1,cases=*|
    #module=m2:suite=*,cases=c11|module=m888:suite=s3,cases=c7"""
    #result = loadTestCases('module=*')
    #print(result)

    my = MyClass()
    my.foo()
    